﻿using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Sgr.DataCategories.Stores
{
    /// <summary>
    /// 基于 GenericLocalCache 的本地数据字典存储实现（多类别）。
    /// </summary>
    public class DefaultLocalDataDictionaryStore : IDataDictionaryStore
    {
        private readonly ConcurrentDictionary<string, GenericLocalCache<IReadOnlyDictionary<string, NameValue>>> _caches = new();
        private readonly ILogger<DefaultLocalDataDictionaryStore> _logger;

        private readonly int _defaultCacheExpirationSeconds = 7200;                 // 2 小时
        private readonly int _defaultBackgroundRefreshTimeoutSeconds = 60;          // 后台刷新超时
        private readonly int _maxCacheItems;

        private readonly SemaphoreSlim _cleanupLock = new(1, 1);
        private int _cleanupInProgress = 0;
        private volatile bool _disposed;

        public DefaultLocalDataDictionaryStore(IConfiguration configuration, ILogger<DefaultLocalDataDictionaryStore> logger)
        {
            Check.NotNull(configuration, nameof(configuration));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));

            _defaultCacheExpirationSeconds = configuration.GetValue("Sgr:Caching:DataDictionaryExpiration", 7200);
            _defaultBackgroundRefreshTimeoutSeconds = configuration.GetValue("Sgr:Caching:DataDictionaryBackgroundRefreshTimeout", 60);
            _maxCacheItems = configuration.GetValue("Sgr:Caching:DataDictionaryMaxCount", 200);
        }

        public async Task<IReadOnlyDictionary<string, NameValue>> GetOrAddAsync(
            string category,
            Func<string, CancellationToken, Task<IReadOnlyDictionary<string, NameValue>>> valueFactory,
            CancellationToken cancellationToken = default)
        {
            CheckDisposed();
            if (string.IsNullOrWhiteSpace(category))
                throw new ArgumentException("类别名称不能为空", nameof(category));

            Check.NotNull(valueFactory, nameof(valueFactory));
            cancellationToken.ThrowIfCancellationRequested();

            var cache = _caches.GetOrAdd(category, CreateCategoryCache);

            // 若是新建类别，触发容量检查
            if (_caches.Count > _maxCacheItems && Interlocked.CompareExchange(ref _cleanupInProgress, 1, 0) == 0)
            {
                _ = Task.Run(async () =>
                {
                    try { await CleanupOldestAsync().ConfigureAwait(false); }
                    finally { Interlocked.Exchange(ref _cleanupInProgress, 0); }
                });
            }

            // 适配 valueFactory：注入 category
            var result = await cache.GetOrAddAsync(async ct =>
            {
                var dict = await valueFactory(category, ct).ConfigureAwait(false)
                           ?? new Dictionary<string, NameValue>();

                // 确保为只读字典实例
                return dict is IReadOnlyDictionary<string, NameValue> ro
                    ? ro
                    : new ReadOnlyDictionary<string, NameValue>(new Dictionary<string, NameValue>(dict));
            }, cancellationToken).ConfigureAwait(false);

            return result;
        }

        public async Task RefreshAsync(
            string category,
            IReadOnlyDictionary<string, NameValue> items,
            CancellationToken cancellationToken = default)
        {
            CheckDisposed();
            if (string.IsNullOrWhiteSpace(category))
                throw new ArgumentException("类别名称不能为空", nameof(category));
            Check.NotNull(items, nameof(items));
            cancellationToken.ThrowIfCancellationRequested();

            var cache = _caches.GetOrAdd(category, CreateCategoryCache);

            var readOnly = items is ReadOnlyDictionary<string, NameValue>
                ? items
                : new ReadOnlyDictionary<string, NameValue>(new Dictionary<string, NameValue>(items));

            await cache.RefreshAsync(readOnly, cancellationToken).ConfigureAwait(false);
            _logger.LogInformation("手动刷新数据字典类别 {Category}，数量：{Count}", category, readOnly.Count);
        }

        public Task RemoveAsync(string category, CancellationToken cancellationToken = default)
        {
            CheckDisposed();
            if (string.IsNullOrWhiteSpace(category))
                throw new ArgumentException("类别名称不能为空", nameof(category));
            cancellationToken.ThrowIfCancellationRequested();

            if (_caches.TryRemove(category, out var cache))
            {
                cache.Dispose();
                _logger.LogDebug("已移除数据字典类别 {Category}", category);
            }
            return Task.CompletedTask;
        }

        public async Task ClearAsync(CancellationToken cancellationToken = default)
        {
            CheckDisposed();
            cancellationToken.ThrowIfCancellationRequested();

            foreach (var kv in _caches.ToArray())
            {
                if (_caches.TryRemove(kv.Key, out var cache))
                    cache.Dispose();
            }

            _logger.LogInformation("已清空所有数据字典缓存类别");
            await Task.CompletedTask;
        }

        private GenericLocalCache<IReadOnlyDictionary<string, NameValue>> CreateCategoryCache(string category)
        {
            _logger.LogDebug("创建数据字典类别缓存：{Category}", category);
            return new GenericLocalCache<IReadOnlyDictionary<string, NameValue>>(
                cacheName: $"DataDictionary:{category}",
                expirationTimeoutSeconds: _defaultCacheExpirationSeconds,
                refreshTimeoutSeconds: _defaultBackgroundRefreshTimeoutSeconds,
                logger: _logger
            );
        }

        private async Task CleanupOldestAsync()
        {
            try
            {
                if (!await _cleanupLock.WaitAsync(TimeSpan.FromMilliseconds(1000)).ConfigureAwait(false))
                    return;

                try
                {
                    var excess = _caches.Count - _maxCacheItems;
                    if (excess <= 0) return;

                    // 优先移除已过期
                    var expiredKeys = _caches
                        .Where(kv => kv.Value.IsExpired)
                        .Select(kv => kv.Key)
                        .Take(excess)
                        .ToList();

                    int removed = 0;
                    foreach (var key in expiredKeys)
                    {
                        if (_caches.TryRemove(key, out var cache))
                        {
                            cache.Dispose();
                            removed++;
                        }
                    }

                    excess = _caches.Count - _maxCacheItems;
                    if (excess > 0)
                    {
                        // 仍超出，按最早过期时间淘汰
                        var oldest = _caches
                            .OrderBy(kv => kv.Value.ExpirationTime)
                            .Take(excess)
                            .Select(kv => kv.Key)
                            .ToList();

                        foreach (var key in oldest)
                        {
                            if (_caches.TryRemove(key, out var cache))
                            {
                                cache.Dispose();
                                removed++;
                            }
                        }
                    }

                    if (removed > 0)
                        _logger.LogInformation("数据字典缓存容量清理：移除 {Removed} 个类别，当前总数 {Count}", removed, _caches.Count);
                }
                finally
                {
                    _cleanupLock.Release();
                }
            }
            catch (Exception ex)
            {
                if (!_disposed)
                    _logger.LogError(ex, "清理数据字典类别缓存时发生错误");
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) return;
            if (disposing)
            {
                foreach (var kv in _caches)
                    kv.Value.Dispose();
                _caches.Clear();
                _cleanupLock.Dispose();
            }
            _disposed = true;
        }

        private void CheckDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
        }
    }
}